package com.study.sbsummary.config.database;

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.study.sbsummary.config.database.shadow.ShadowConstants;
import com.study.sbsummary.config.database.shadow.ShadowInterceptor;
import com.study.sbsummary.context.RequestContext;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

import java.lang.reflect.Method;
import java.util.Set;

/**
 * mongo news库数据源配置
 * https://blog.csdn.net/qq_35124119/article/details/83210772
 */
@Slf4j
@Configuration
@ConditionalOnBean(ShadowInterceptor.class)
@EnableMongoRepositories(mongoTemplateRef = DocDetailMongoConfig.PROXY_DB_TEMPLATE, basePackages = "com.truely.plog" +
        ".serving.mapper.news")
public class DocDetailMongoConfig {
    private static final String MAIN_DB_FACTORY = "mainDocDetailMongoFactory";
    private static final String SHADOW_DB_FACTORY = "shadowDocDetailMongoFactory";

    private static final String MAIN_DB_TEMPLATE = "mainDocDetailMongoTemplate";
    private static final String SHADOW_DB_TEMPLATE = "shadowDocDetailMongoTemplate";
    public static final String PROXY_DB_TEMPLATE = "proxyDocDetailMongoTemplate";

    @Autowired
    private ApplicationContext appContext;

    @Value("${spring.datasource.mongodb.doc-news.uri}")
    private String uri;

    @Value("${spring.datasource.mongodb.doc-news.username}")
    private String username;

    @Value("${spring.datasource.mongodb.doc-news.password}")
    private String password;

    @Value("${spring.datasource.mongodb.doc-news.database}")
    private String database;

    @Value("${spring.datasource.mongodb.doc-news.verifyDb}")
    private String verifyDb;

    @Autowired
    private ShadowInterceptor shadowInterceptor;

    //先写死基于影子表，如果要根据影子库的话放开注释即可
    private boolean isShadowTable() {
        return true;
//        return shadowInterceptor.isShadowTable();
    }

    //先写死基于影子表，如果要根据影子库的话放开注释即可
    private boolean isShadowDatabase() {
        return false;
//        return shadowInterceptor.isShadowDatabase();
    }

    @Bean(name = MAIN_DB_FACTORY)
    @Primary
    public MongoDatabaseFactory mainMongoFactory() {
        try {
            String url = String.format("mongodb://%s:%s@%s/%s?authSource=%s",
                    username, password.replaceAll("%", "%25"), uri, database, verifyDb);
            return new SimpleMongoClientDatabaseFactory(url);
        } catch (Exception e) {
            log.error("init mongo error：", e);
            throw e;
        }
    }

    @Bean(name = SHADOW_DB_FACTORY)
    @ConditionalOnBean(ShadowInterceptor.class)
    public MongoDatabaseFactory shadowMongoFactory() {
        if (!isShadowDatabase()) {
            return null;
        }
        if (!ShadowInterceptor.isSupportOnlineTest() || !isShadowDatabase()) {
            return null;
        }
        try {
            String url = String.format("mongodb://%s:%s@%s/%s?authSource=%s",
                    username, password.replaceAll("%", "%25"), uri, ShadowConstants.PRESSURE_TEST_PREFIX + database, verifyDb);
            return new SimpleMongoClientDatabaseFactory(url);
        } catch (Exception e) {
            log.error("init mongo error：", e);
            throw e;
        }
    }

    private MongoTemplate createTemplate(MongoDatabaseFactory mongoDatabaseFactory) {
        try {
            MongoMappingContext mongoMappingContext = new MongoMappingContext();
            mongoMappingContext.setApplicationContext(appContext);

            MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDatabaseFactory), mongoMappingContext);
            converter.setTypeMapper(new DefaultMongoTypeMapper(null));

            if (isShadowTable()) {
                return new TruelyMongoTemplate(mongoDatabaseFactory, converter);
            }
            return new MongoTemplate(mongoDatabaseFactory, converter);
        } catch (Exception e) {
            log.error("init mongo error：", e);
            throw e;
        }
    }

    @Primary
    @Bean(name = PROXY_DB_TEMPLATE)
    @ConditionalOnBean(ShadowInterceptor.class)
    public MongoTemplate mongoTemplate(@Autowired @Qualifier(MAIN_DB_TEMPLATE) MongoTemplate mainTemplate,
                                       @Autowired(required = false) @Qualifier(SHADOW_DB_TEMPLATE) MongoTemplate shadowTemplate) {
        if (isShadowDatabase()) {
            return new ProxyMongoTemplate(mainTemplate, shadowTemplate);
        }
        return mainTemplate;
    }

    @Bean(name = SHADOW_DB_TEMPLATE)
    @ConditionalOnBean({ShadowInterceptor.class})
    public MongoTemplate shadowTemplate(@Autowired(required = false) @Qualifier(SHADOW_DB_FACTORY) MongoDatabaseFactory shadowFactory) {
        if (shadowFactory != null && isShadowDatabase()) {
            return createTemplate(shadowFactory);
        }
        return null;
    }

    /**
     * 只在影子collection场景下使用
     *
     * @param mainFactory
     * @return
     */
    @Bean(name = MAIN_DB_TEMPLATE)
    @ConditionalOnBean({ShadowInterceptor.class})
    public MongoTemplate mainTemplate(@Autowired @Qualifier(MAIN_DB_FACTORY) MongoDatabaseFactory mainFactory) {
        return createTemplate(mainFactory);
    }

    public static class TruelyMongoTemplate extends MongoTemplate {
        public TruelyMongoTemplate(MongoClient mongoClient, String databaseName) {
            super(mongoClient, databaseName);
        }

        public TruelyMongoTemplate(MongoDatabaseFactory mongoDbFactory) {
            super(mongoDbFactory);
        }

        public TruelyMongoTemplate(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter) {
            super(mongoDbFactory, mongoConverter);
        }

        @Override
        public String getCollectionName(Class<?> entityClass) {
            return ShadowInterceptor.checkAndGetShadowColName(super.getCollectionName(entityClass));
        }
    }

    /**
     * 影子库场景下使用
     */
    private class ProxyMongoTemplate extends MongoTemplate {
        private MongoTemplate mainTemplate;
        private MongoTemplate shadowTemplate;

        public ProxyMongoTemplate(MongoTemplate mainTemplate, MongoTemplate shadowTemplate) {
            super(mainTemplate.getMongoDbFactory(), mainTemplate.getConverter());
            this.mainTemplate = mainTemplate;
            this.shadowTemplate = shadowTemplate;
        }

        private MongoTemplate getTemplate() {
            if (RequestContext.isOnlineTest() && isShadowDatabase()) {
                return shadowTemplate;
            }
            return mainTemplate;
        }

        @Override
        public MongoDatabaseFactory getMongoDbFactory() {
            return getTemplate().getMongoDbFactory();
        }

        @Override
        public MongoConverter getConverter() {
            return getTemplate().getConverter();
        }

        @Override
        protected MongoDatabase doGetDatabase() {
            return getTemplate().getDb();
        }

        @Override
        public <T> GeoResults<T> geoNear(NearQuery near, Class<?> domainType, String collectionName, Class<T> returnType) {
            return getTemplate().geoNear(near, domainType, collectionName, returnType);
        }

        @Override
        public Set<String> getCollectionNames() {
            return getTemplate().getCollectionNames();
        }

        @Override
        public PersistenceExceptionTranslator getExceptionTranslator() {
            return getTemplate().getExceptionTranslator();
        }

        @Override
        public MongoCollection<Document> getCollection(String collectionName) {
            return getTemplate().getCollection(collectionName);
        }
    }

    /**
     * 想通过cglib代理，但是发现通过JPA方式没法拦截。
     */
    public class MongoTemplateProxy implements MethodInterceptor {
        private MongoTemplate mainTemplate;
        private MongoTemplate shadowTemplate;

        public MongoTemplateProxy(MongoTemplate mainTemplate, MongoTemplate shadowTemplate) {
            this.mainTemplate = mainTemplate;
            this.shadowTemplate = shadowTemplate;
        }


        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            if (RequestContext.isOnlineTest()) {
                return method.invoke(shadowTemplate, objects);
            }
            return method.invoke(mainTemplate, objects);
        }
    }
}
